﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using UCS.XNA.Utils.Animation;

namespace UCS.XNA.Utils
{
    public class TextBox : IDrawable, IFadeAnimatable
    {
        #region Constants

        private const float TOP_MARGIN_PERCENTAGE = 0.1f;
        private const float LEFT_MARGIN_PERCENTAGE = 0.02f;
        private const int REPEAT_KEY_MILISECONDS = 500;

        #endregion

        #region Private Members

        private Texture2D _texture;
        private Vector2 _textPosition;
        private Rectangle _textBounds;
        private RasterizerState _rasterizerState;
        private KeyboardState _lastKeyboardState;
        private Dictionary<Keys, int> _keysPressingTime;
        private FadeAnimation _fadeAnimation;

        #endregion

        #region Attributes and Properties

        private string _text;
        public string Text
        {
            get { return this._text; }
            set
            {
                this._text = value;

                this.InitializeTextPosition();
            }
        }

        private Rectangle _bounds;
        public Rectangle Bounds
        {
            get { return this._bounds; }
            set
            {
                this._bounds = value;
                this.InitializeTextPosition();
            }
        }

        private SpriteFont _spriteFont;
        public SpriteFont SpriteFont
        {
            get { return this._spriteFont; }
            set { this._spriteFont = value; }
        }

        public int DrawOrder
        {
            get { throw new NotImplementedException(); }
        }

        private bool _visible;
        public bool Visible
        {
            get { return this._visible; }
        }

        private bool _enabled;
        public bool Enabled
        {
            get { return this._enabled; }
        }

        private bool _focused;
        public bool Focused
        {
            get { return this._focused; }
            set { this._focused = value; }
        }

        private bool _readOnly;
        public bool ReadOnly
        {
            get { return this._readOnly; }
            set { this._readOnly = value; }
        }

        private float _transparency;
        public float Transparency
        {
            get { return this._transparency; }
            set { this._transparency = value; }
        }

        #endregion

        #region Events

        public event EventHandler<EventArgs> DrawOrderChanged;
        public event EventHandler<EventArgs> VisibleChanged;

        #endregion

        #region Constructors

        public TextBox(Rectangle bounds, SpriteFont spriteFont)
        {
            this._bounds = bounds;
            this._text = string.Empty;
            this._spriteFont = spriteFont;
            this._rasterizerState = new RasterizerState() { ScissorTestEnable = true };
            this._keysPressingTime = new Dictionary<Keys, int>();

            this.InitializeTexture();
            this.InitializeTextPosition();
        }

        #endregion

        #region Private Methods

        private void InitializeTexture()
        {
            this._texture = new Texture2D(GameResources.Graphics.GraphicsDevice, 1, 1);
            this._texture.SetData(new Color[] { Color.White });
        }

        private void InitializeTextPosition()
        {
            var textSize = SpriteFont.MeasureString(this._text);
            
            int x = (int)(this._bounds.X + (this._bounds.Width * LEFT_MARGIN_PERCENTAGE));
            int y = (int)(this._bounds.Y + (this._bounds.Height / 2 - textSize.Y / 2));
            this._textPosition = new Vector2(x, y);

            this._textBounds = new Rectangle();
            this._textBounds.X = (int)this._textPosition.X;
            this._textBounds.Y = (int)this._textPosition.Y;
            this._textBounds.Width = (int)(this._bounds.Width - (this.Bounds.Width * LEFT_MARGIN_PERCENTAGE * 2));
            this._textBounds.Height = (int)textSize.Y;
        }

        private void DrawBackground()
        {
            GameResources.SpriteBatch.Begin();

            GameResources.SpriteBatch.Draw(this._texture, this._bounds, Color.LightGray * this._transparency);

            GameResources.SpriteBatch.End();
        }

        private void DrawText()
        {
            GameResources.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, null, null, this._rasterizerState);

            Rectangle currentRect = GameResources.Graphics.GraphicsDevice.ScissorRectangle;
            GameResources.Graphics.GraphicsDevice.ScissorRectangle = this._textBounds;

            GameResources.SpriteBatch.DrawString(this._spriteFont, this._text, this._textPosition, new Color(63, 64, 66) * this._transparency);

            GameResources.Graphics.GraphicsDevice.ScissorRectangle = currentRect;

            GameResources.SpriteBatch.End();
        }

        private void DrawCursor()
        {
            var textSize = SpriteFont.MeasureString(this._text);
        }

        private void RemoveDrepessedKeys(KeyboardState keyboardState)
        {
            var removedKeys = new List<Keys>();

            foreach (var key in this._keysPressingTime.Keys)
                if (keyboardState.IsKeyUp(key))
                    removedKeys.Add(key);

            foreach (var key in removedKeys)
                this._keysPressingTime.Remove(key);
        }

        #endregion

        #region Public Methods

        public void Update(GameTime gameTime, KeyboardState keyboardState)
        {
            if (this._enabled)
            {
                if (this._focused && !this._readOnly)
                {
                    this.RemoveDrepessedKeys(keyboardState);
                    var pressedKeys = keyboardState.GetPressedKeys();

                    for (int i = 0; i < pressedKeys.Length; i++)
                    {
                        Keys key = pressedKeys[i];
                        if (!this._keysPressingTime.ContainsKey(key))
                            this._keysPressingTime.Add(key, 0);

                        if (this._lastKeyboardState.IsKeyUp(key) || this._keysPressingTime[key] > REPEAT_KEY_MILISECONDS)
                        {
                            if ((key >= Keys.A && key <= Keys.Z) || (key >= Keys.D0 && key <= Keys.D9) || (key >= Keys.NumPad0 && key <= Keys.NumPad9) || key == Keys.OemPeriod || key == Keys.Space)
                            {
                                if ((!pressedKeys.Contains(Keys.LeftShift) && !pressedKeys.Contains(Keys.RightShift)) && (key >= Keys.A && key <= Keys.Z))
                                    this._text += Convert.ToChar(key.ToChar() + 32);
                                else
                                    this._text += key.ToChar();

                                this.InitializeTextPosition();
                            }
                            else if (key == Keys.Back && this._text.Length > 0)
                                this._text = this._text.Remove(this._text.Length - 1);
                        }

                        this._keysPressingTime[key] += gameTime.ElapsedGameTime.Milliseconds;
                    }
                }

                this._lastKeyboardState = keyboardState;
            }

            if (this._fadeAnimation != null)
                this._fadeAnimation.Update(gameTime);
        }

        public void Draw(GameTime gameTime)
        {
            if (this._visible)
            {
                this.DrawBackground();
                this.DrawText();
            }
        }

        public void Hide()
        {
            this._enabled = false;
            this._fadeAnimation = new FadeOutAnimation(this, 0.05f);
            this._fadeAnimation.AnimationFinished += (sender, e) => { this._visible = false; };
        }

        public void Show()
        {
            this._visible = true;
            this._enabled = true;

            this._fadeAnimation = new FadeInAnimation(this, 0.05f);
        }

        #endregion
    }
}
